﻿/*	
	stencil - action script 3 isometric game engine.

	http://code.google.com/p/stencilas3/
	
	Copyright (c) 2010 Max Zhuravkov, All Rights Reserved.

	Permission is hereby granted, free of charge, to any person obtaining a copy of
	this software and associated documentation files (the "Software"), to deal in
	the Software without restriction, including without limitation the rights to
	use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
	of the Software, and to permit persons to whom the Software is furnished to do
	so, subject to the following conditions:

	The above copyright notice and this permission notice shall be included in all
	copies or substantial portions of the Software.

	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
	SOFTWARE.
*/
package stencil.core.engine.display 
{
	import flash.display.DisplayObject;
	import flash.display.DisplayObjectContainer;
	import flash.display.Sprite;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import flash.events.EventDispatcher;
	import flash.utils.Dictionary;
	import stencil.core.engine.IGrid;
	import stencil.core.errors.ArgumentIsNullError;
	import stencil.core.errors.NotImplementedMethodError;
	import stencil.core.errors.RequiredPropertyError;
	
	/**
	 * Defines operations for managing display list content such as adding, removing, updating and sorting. 
	 * Implementation of these methods should be provided by subclasses considering the following:
	 * 
	 * Render phase of dislay list is splitted into six (or five when depth sort is omitted) stages.
	 * 1) pre-render stage (preRender method).
	 * 2) calculating positions of displayobjects and determining their visibility. 
	 * if depth sorting should be performed both 4a and 4b executed. otherwise only 4b.
	 * 4a) depth sorting of visible objects.
	 * 4b) for each visible display object its render method is called.
	 * 5) post-render stage (postRender method).
	 * 
	 * @author Max Zhuravkov
	 */
	public class AbstractDisplayList extends EventDispatcher implements IDisplayList {
		
		private var _id:String;
		
		private var _grid:IGrid;
		
		private var _container:DisplayObjectContainer;
		
		private var _calculateTilePositions:Boolean;
		
		private var _callbacks:Array = [];
		
		private var _depthSortingAlgorithm:IDepthSorting;
		
		private var _displayPosition:Point = new Point();
		
		private var _dx:Number = 0;
		
		private var _dy:Number = 0;
		
		private var _viewPort:Rectangle = new Rectangle();
		
		protected var flagSort:Boolean = false;
		
		//----------------------------------------------------------------
		// Constructor
		//----------------------------------------------------------------
		
		public function AbstractDisplayList(id:String = null, calculateTilePositions:Boolean = false) {
			super();
			
			this._container = new Sprite();
			if(id != null){
				this._container.name = id;
			}
			this._id = this._container.name;
			this._calculateTilePositions = calculateTilePositions;
			
			this.depthSortingAlgorithm = this.getDefaultDepthSortingAlgorithm();
		}
		
		//------------------------------------------------------------------------------------------------
		// Public methods
		//------------------------------------------------------------------------------------------------
		
		/**
		 * @inheritDoc
		 */
		public final function get id():String {
			return this._id;
		}
		
		/**
		 * @inheritDoc
		 */
		public final function set grid(value:IGrid):void {
			if (this._grid == value) {
				return;
			}
			this._grid = value;
		}
		
		public final function get grid():IGrid {
			return this._grid;
		}
		
		/**
		 * @inheritDoc
		 */
		public final function get calculateTilePositions():Boolean {
			return this._calculateTilePositions;
		}
		
		/**
		 * @inheritDoc
		 */
		public final function get content():DisplayObject {
			return this._container;
		}
		
		/**
		 * @inheritDoc
		 */
		public function get children():Array {
			throw new Error("Get method 'children' has not been implemented yet.")
			
			return null;
		}
		
		/**
		 * @inheritDoc
		 */
		public function get tiles():Array {
			throw new Error("Get method 'tiles' has not been implemented yet.")
			
			return null;
		}
		
		/**
		 * @inheritDoc
		 */
		public function set depthSortingAlgorithm(value:IDepthSorting):void {
			this._depthSortingAlgorithm = value;
		}
		
		/**
		 * @inheritDoc
		 */
		public function get depthSortingAlgorithm():IDepthSorting {
			return this._depthSortingAlgorithm;
		}
		
		/**
		 * @inheritDoc
		 */
		public final function get displayPosition():Point {
			this._displayPosition.x = this._dx;
			this._displayPosition.y = this._dy;
			return this._displayPosition;
		}
		
		//------------------------------------------------------------------------------------------------
		// Public methods
		//------------------------------------------------------------------------------------------------
		
		/**
		 * @inheritDoc
		 */
		public function add(child:IDisplayObject):void {
			throw new NotImplementedMethodError("add");
		}
		
		/**
		 * @inheritDoc
		 */
		public function update(child:IDisplayObject):void {
			throw new NotImplementedMethodError("update");
		}
		
		/**
		 * @inheritDoc
		 */
		public function invalidate():void {
			throw new NotImplementedMethodError("invalidate");
		}
		
		/**
		 * @inheritDoc
		 */
		public function remove(child:IDisplayObject):void {
			throw new NotImplementedMethodError("remove");
		}
		
		/**
		 * @inheritDoc
		 */
		public function contains(child:IDisplayObject):Boolean {
			throw new NotImplementedMethodError("contains");
			
			return false;
		}
		
		/**
		 * @inheritDoc
		 */
		public function find(id:String):IDisplayObject {
			throw new NotImplementedMethodError("find");
			
			return null;
		}
		
		/**
		 * @inheritDoc
		 */
		public function clear():void {
			throw new NotImplementedMethodError("clear");
		}
		
		/**
		 * @inheritDoc
		 */
		public function sort():void {
			this.flagSort = true;
		}
		
		/**
		 * @inheritDoc
		 */
		public function addRenderCallback(callback:Function):void {
			if (callback == null) {
				throw new ArgumentIsNullError("callback");
			}
			
			if (this._callbacks.indexOf(callback) == -1) {
				this._callbacks.push(callback);
			}
		}
		
		/**
		 * @inheritDoc
		 */
		public function removeRenderCallback(callback:Function):void {
			if (callback == null) {
				throw new ArgumentIsNullError("callback");
			}
			
			var index:int = this._callbacks.indexOf(callback);
			if (index == -1) {
				this._callbacks.splice(index, 1);
			}
		}
		
		public final function displayAt(dx:Number, dy:Number):void {
			this._dx = dx;
			this._dy = dy;
			
			this.container.x = dx;
			this.container.y = dy;
		}
		
		/**
		 * Renders display list. 
		 * @param	viewPort	view port bounds.
		 * @param	time		time.
		 */
		public final function render(viewPort:Rectangle, time:Number):void {
			if (!viewPort) {
				throw new ArgumentIsNullError("viewPort");
			}
			
			if (!this.grid) {
				throw new RequiredPropertyError(this, "grid");
			}
			
			var viewPortChanged:Boolean = !this._viewPort.equals(viewPort);
			
			this._viewPort.x = viewPort.x;
			this._viewPort.y = viewPort.y;
			this._viewPort.width = viewPort.width;
			this._viewPort.height = viewPort.height;
			
			this.preRender(viewPort);
			
			var visibleObjects:Array = this.updateDisplayObjects(viewPort, viewPortChanged);
			
			if (this.flagSort) {
				this.sortAndRenderDisplayObjects(visibleObjects, viewPort, time);
				
				this.flagSort = false;
			}else {
				for each(var object:IDisplayObject in visibleObjects) {
					if(object.enabled){
						object.render(viewPort, time);
					}
				}
			}
			
			this.postRender(viewPort);
		}
		
		//------------------------------------------------------------------------------------------------
		// Protected properties
		//------------------------------------------------------------------------------------------------
		
		protected final function get container():DisplayObjectContainer {
			return this._container;
		}
	
		
		//------------------------------------------------------------------------------------------------
		// Protected methods
		//------------------------------------------------------------------------------------------------
		
		protected function getDefaultDepthSortingAlgorithm():IDepthSorting {
			return new DefaultDepthSorting();
		}
		
		protected function preRender(viewPort:Rectangle):void {
			
		}
		
		protected function postRender(viewPort:Rectangle):void {
			
		}
		
		protected function updateDisplayObjects(viewPort:Rectangle, viewPortChanged:Boolean):Array {
			throw new NotImplementedMethodError("updateDisplayObjects")
			
			return null;
		}
		
		protected function sortAndRenderDisplayObjects(visibleObjectStates:Array, viewPort:Rectangle, time:Number):void {
			throw new NotImplementedMethodError("sortDisplayObjects")
		}
		
		protected function findVisibleObjects(viewPort:Rectangle, states:Array):Array {
			throw new NotImplementedMethodError("findVisibleObjects");
			return null;
		}
		
	}
	
}